CommonParams解决方案
在实现业务框架的时候,经常需要支持传参的功能(比如 Buff
系统中,AddBuff
的时候,通常需要支持外部传入一个 BuffParams
,在内部解析出各种 Param
使用。)
这个参数需要支持基本的数据保存、同步等。
初始的想法
一个最简单的想法,是打包一个结构体,支持各种类型的传入,比如:
1 2 3 4 5 6 7 8 9 10
| USTRUCT() struct Params { UPROPERTY() uint64 uVar0; UPROPERTY() int iVar0; UPROPERTY() float fVar0; }
|
这样虽然很简单,同时可以通过反射来同步。
但是有一个巨大的问题,那就是在解析参数的时候,只知道 Var0
这种没有名字的抽象的概念,使用者需要记住 Var0
对应的是什么数据,很不直观。
CommonVariantParams
维护一个 CommonVariantParams
,通过 TMap <FString, FVariant> ValueMap
来保存数据。
为了方便使用,还可以自定义一些构造函数,比如 std::initializer_list<TPairInitializer<const FString&, FVariant>> ValuePairs
,这样就可以支持 { {Key0, Val0}, {Key1, Val1} }
形式的构造。
由于 TMap
以及 FVariant
实际上在引擎内部都已经有了合适的重载 operator << Archive
,所以自定义 NetSerialize
也很简单。
同时,标记 WithNetSerializer
来自定义 NetSerialize
以支持网络同步;标记 WithIdenticalViaEquality
,在进行同步计算 RepLayout
时,通过自定义的 operator ==
来进行 Diff
,这样当 CommonVariantParams
作为属性同步时,才能正常判断其发生了变化以进行属性同步。
使用方法
1 2 3 4 5 6
| FCommonVariantParams Params = { {"ParamA", (float)A}, {"ParamsB", (int)B }, {"ParamsC", (FString)C } });
float A = BuffParams.GetValue<float>("ParamA"); int B = BuffParams.GetValue<int>("ParamB"); FString C = BuffParams.GetValue<FString>("ParamC");
|
具体实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| USTRUCT(BlueprintType) struct FCommonVariantParams { GENERATED_BODY()
FCommonVariantParams() = default; FCommonVariantParams(const FString& Key, FVariant Value); FCommonVariantParams(std::initializer_list<TPairInitializer<const FString&, FVariant>> ValuePairs);
void SetValue(const FString& FieldName, FVariant Value); bool Contains(const FString& FieldName) const; bool IsEmpty() const; FVariant GetValue(const FString& FieldName) const; template<typename ValueType> ValueType GetValue(const FString& FieldName, ValueType Default = {}) const { if (!ValueMap.Contains(FieldName)) return Default; if (TVariantTraits<ValueType>::GetType() != ValueMap[FieldName].GetType()) return Default; return ValueMap[FieldName].GetValue<ValueType>(); } template <typename ValueType> bool TryGetValue(const FString& FieldName, ValueType& OutValue) const { if (!ValueMap.Contains(FieldName)) return false; if (TVariantTraits<ValueType>::GetType() != ValueMap[FieldName].GetType()) return false; OutValue = ValueMap[FieldName].GetValue<ValueType>(); return true; } FString ToString() const; const TMap<FString, FVariant>& GetValueMap() const;
bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess); bool operator==(const FCommonVariantParams& Other) const; bool operator!=(const FCommonVariantParams& Other) const { return !(*this == Other); }
FCommonVariantParams operator+(const FCommonVariantParams& OtherParams); FCommonVariantParams& operator=(const FCommonVariantParams& OtherParams) { ValueMap = OtherParams.GetValueMap(); return *this; }
void Merge(const FCommonVariantParams& OtherParams); void Clear();
decltype(auto) begin() { return ValueMap.begin(); } decltype(auto) begin() const { return ValueMap.begin(); } decltype(auto) end() { return ValueMap.end(); } decltype(auto) end() const { return ValueMap.end(); }
protected: TMap<FString, FVariant> ValueMap; };
template<> struct TStructOpsTypeTraits<FCommonVariantParams> : TStructOpsTypeTraitsBase2<FCommonVariantParams> { enum { WithNetSerializer = true, WithIdenticalViaEquality = true, }; };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| FCommonVariantParams::FCommonVariantParams(const FString& Key, FVariant Value) { ValueMap.Add(Key, Value); }
FCommonVariantParams::FCommonVariantParams(std::initializer_list<TPairInitializer<const FString&, FVariant>> ValuePairs) { for (const auto& Pair : ValuePairs) { ValueMap.Add(Pair.Key, Pair.Value); } }
void FCommonVariantParams::SetValue(const FString& FieldName, FVariant Value) { ValueMap.Add(FieldName, FVariant(Value)); }
bool FCommonVariantParams::Contains(const FString& FieldName) const { return ValueMap.Contains(FieldName); }
bool FCommonVariantParams::IsEmpty() const { return ValueMap.IsEmpty(); }
FVariant FCommonVariantParams::GetValue(const FString& FieldName) const { if (auto Value = ValueMap.Find(FieldName)) { return *Value; } return {}; }
const TMap<FString, FVariant>& FCommonVariantParams::GetValueMap() const { return ValueMap; }
bool FCommonVariantParams::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) { bOutSuccess = true; Ar << ValueMap; return true; }
bool FCommonVariantParams::operator==(const FCommonVariantParams& Other) const { TArray<FString> Keys, OtherKeys; ValueMap.GenerateKeyArray(Keys); Other.ValueMap.GenerateKeyArray(OtherKeys);
if (Keys != OtherKeys) return false; for (auto Key : Keys) { if (ValueMap[Key] != Other.ValueMap[Key]) return false; }
return true; }
FCommonVariantParams FCommonVariantParams::operator+(const FCommonVariantParams& OtherParams) { FCommonVariantParams CombinedParams = *this; CombinedParams.Merge(OtherParams); return CombinedParams; }
void FCommonVariantParams::Merge(const FCommonVariantParams& OtherParams) { for (const auto& [Key, Value] : OtherParams.GetValueMap()) { SetValue(Key, Value); } }
void FCommonVariantParams::Clear() { ValueMap.Empty(); }
|
Lua
对于这个 CommonVariantParams
,还可以扩展一些方法,让这些参数可以通过 Unlua
在 Lua
脚本中设置与访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void SetIntValue(const FString& FieldName, int32 Value); void SetUInt64Value(const FString& FieldName, uint64 Value); void SetFloatValue(const FString& FieldName, float Value); void SetBoolValue(const FString& FieldName, bool Value); void SetStringValue(const FString& FieldName, const FString& Value); void SetTransformValue(const FString& FieldName, const FTransform& Value); void SetVectorValue(const FString& FieldName, const FVector& Value);
int32 GetIntValue(const FString& FieldName, int32 Default = {}) const; uint64 GetUInt64Value(const FString& FieldName, uint64 Default = {}) const; float GetFloatValue(const FString& FieldName, float Default = {}) const; bool GetBoolValue(const FString& FieldName, bool Default = {}) const; FString GetStringValue(const FString& FieldName, const FString& Default = {}) const; FTransform GetTransformValue(const FString& FieldName, const FTransform& Default = {}) const; FVector GetVectorValue(const FString& FieldName, const FVector& Default = {}) const;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| void FCommonVariantParams::SetIntValue(const FString& FieldName, int32 Value) { SetValue(FieldName, Value); }
void FCommonVariantParams::SetUInt64Value(const FString& FieldName, uint64 Value) { SetValue(FieldName, Value); }
void FCommonVariantParams::SetFloatValue(const FString& FieldName, float Value) { SetValue(FieldName, Value); }
void FCommonVariantParams::SetBoolValue(const FString& FieldName, bool Value) { SetValue(FieldName, Value); }
void FCommonVariantParams::SetStringValue(const FString& FieldName, const FString& Value) { SetValue(FieldName, Value); }
void FCommonVariantParams::SetTransformValue(const FString& FieldName, const FTransform& Value) { SetValue(FieldName, Value); }
void FCommonVariantParams::SetVectorValue(const FString& FieldName, const FVector& Value) { SetValue(FieldName, Value); }
int32 FCommonVariantParams::GetIntValue(const FString& FieldName, int32 Default ) const { return GetValue<int32>(FieldName, Default); }
uint64 FCommonVariantParams::GetUInt64Value(const FString& FieldName, uint64 Default ) const { return GetValue<uint64>(FieldName, Default); }
float FCommonVariantParams::GetFloatValue(const FString& FieldName, float Default ) const { return GetValue<float>(FieldName, Default); }
bool FCommonVariantParams::GetBoolValue(const FString& FieldName, bool Default ) const { return GetValue<bool>(FieldName, Default); }
FString FCommonVariantParams::GetStringValue(const FString& FieldName, const FString& Default ) const { return GetValue<FString>(FieldName, Default); }
FTransform FCommonVariantParams::GetTransformValue(const FString& FieldName, const FTransform& Default ) const { return GetValue<FTransform>(FieldName, Default); }
FVector FCommonVariantParams::GetVectorValue(const FString& FieldName, const FVector& Default ) const { return GetValue<FVector>(FieldName, Default); }
|
同时,可以添加自定义的 Lua
方法,让其在 Lua
中可以支持 ToTable
等,参考 UnLua & C++ 交互 ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| static FCommonVariantParams* GetCommonVariantParams(lua_State* L, int32 Index) { return UUnLuaFunctionLibrary::GetValue<FCommonVariantParams>(L, Index); }
static int32 FCommonVariantParams_Index(lua_State* L) { if (!UUnLuaFunctionLibrary::CheckParamsCount(L, 2)) return luaL_error(L, "invalid parameters");
auto CommonVariantParams = GetCommonVariantParams(L, 1); if (CommonVariantParams == nullptr) return luaL_error(L, "invalid struct type");
const char* KeyStringPtr = nullptr;
if (lua_isstring(L, 2)) { KeyStringPtr = lua_tostring(L, 2); }
if (KeyStringPtr == nullptr) { lua_pushnil(L); return 1; }
auto Key = FString(UTF8_TO_TCHAR(KeyStringPtr)); if (CommonVariantParams->Contains(Key)) { auto Value = CommonVariantParams->GetValue(Key); UUnLuaFunctionLibrary::PushValue(L, Value); } else { lua_getmetatable(L, 1); if (lua_compare(L, 1, -1, LUA_OPEQ)) lua_pushnil(L); else { lua_pushvalue(L, 2); lua_rawget(L, -2); } lua_remove(L, -2); } return 1; }
static int FCommonVariantParams_ToTable(lua_State* L) { if (!UUnLuaFunctionLibrary::CheckParamsCount(L, 1)) return luaL_error(L, "invalid parameters");
auto CommonVariantParams = GetCommonVariantParams(L, 1); if (CommonVariantParams == nullptr) return luaL_error(L, "invalid struct type");
lua_newtable(L); for (const auto& [Key, Value] : *CommonVariantParams) { UnLua::Push(L, Key); UUnLuaFunctionLibrary::PushValue(L, Value); lua_rawset(L, -3); } return 1; }
static const luaL_Reg FCommonVariantParamsLib[] = { {"ToTable", FCommonVariantParams_ToTable}, {"__index", FCommonVariantParams_Index}, {nullptr, nullptr} };
|
最后进行导出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
BEGIN_EXPORT_REFLECTED_CLASS(FCommonVariantParams) ADD_LIB(FCommonVariantParamsLib)
ADD_FUNCTION(Contains) ADD_FUNCTION(IsEmpty) ADD_FUNCTION(ToString)
ADD_FUNCTION(SetIntValue) ADD_FUNCTION(SetUInt64Value) ADD_FUNCTION(SetFloatValue) ADD_FUNCTION(SetBoolValue) ADD_FUNCTION(SetStringValue) ADD_FUNCTION(SetTransformValue)
ADD_FUNCTION(GetIntValue) ADD_FUNCTION(GetUInt64Value) ADD_FUNCTION(GetFloatValue) ADD_FUNCTION(GetBoolValue) ADD_FUNCTION(GetStringValue) ADD_FUNCTION(GetTransformValue) END_EXPORT_CLASS() IMPLEMENT_EXPORTED_CLASS(FCommonVariantParams)
|
具体业务
特别地,具体业务使用的时候,可以继承一个自己的 Params
来使用,以 FGameplayBuffParams
为例:
1 2 3 4 5 6
| USTRUCT() struct FGameplayBuffParams : public FCommonVariantParams { GENERATED_BODY() using FCommonVariantParams::FCommonVariantParams; }
|